篇首语:本文由编程笔记#小编为大家整理,主要介绍了MySQL 索引及优化实战相关的知识,希望对你有一定的参考价值。
从物理存储角度上,索引可以分为聚集索引和非聚集索引。
1.聚集索引(Clustered Index)
聚集索引决定数据在磁盘上的物理排序,一个表只能有一个聚集索引。
2.非聚集索引(Non-clustered Index)
非聚集索引并不决定数据在磁盘上的物理排序,索引上只包含被建立索引的数据,以及一个行定位符 row-locator,这个行定位符,可以理解为一个聚集索引物理排序的指针,通过这个指针,可以找到行数据。
从逻辑角度,索引可以分为以下几种。
可以通过以下规则对 MySQL 索引进行优化。
1.前导模糊查询不能使用索引。
例如下面 SQL 语句不能使用索引。
select * from doc where title like '%XX'
而非前导模糊查询则可以使用索引,如下面的 SQL 语句。
select * from doc where title like 'XX%'
页面搜索严禁左模糊或者全模糊,如果需要可以用搜索引擎来解决。
2.union、in、or 都能够命中索引,建议使用 in。
示例代码如下:
select * from doc where status=1
union all
select * from doc where status=2
直接告诉 MySQL 怎么做,MySQL 耗费的 CPU 最少,但是一般不这么写 SQL。
示例代码如下:
select * from doc where status in (1, 2)
查询优化耗费的 CPU 比 union all 多,但可以忽略不计,一般情况下建议使用 in
示例代码如下:
select * from doc where status = 1 or status = 2
查询优化耗费的 CPU 比 in 多,不建议频繁用 or。
3.负向条件查询不能使用索引,可以优化为 in 查询。
负向条件有&#xff1a;!&#61;、<>、not in、not exists、not like 等。
例如下面代码&#xff1a;
select * from doc where status !&#61; 1 and status !&#61; 2
可以优化为 in 查询&#xff1a;
select * from doc where status in (0,3,4)
4.联合索引最左前缀原则&#xff08;又叫最左侧查询&#xff09;
例如登录业务需求&#xff0c;代码如下。
select uid, login_time from user where login_name&#61;? and passwd&#61;?
可以建立(login_name
, passwd)的联合索引。
因为业务上几乎没有 passwd 的单条件查询需求&#xff0c;而有很多 login_name
的单条件查询需求&#xff0c;所以可以建立(login_name
, passwd)的联合索引&#xff0c;而不是(passwd, login_name
)。
下面的 SQL 语句也可以命中 (login_name
, passwd) 这个联合索引。
select uid, login_time from user where passwd&#61;? and login_name&#61;?
但还是建议 where 后的顺序和联合索引一致&#xff0c;养成好习惯。
5.范围列可以用到索引&#xff08;联合索引必须是最左前缀&#xff09;。
假如有联合索引 (empno、title、fromdate)&#xff0c;那么下面的 SQL 中 emp_no
可以用到索引&#xff0c;而 title 和 from_date 则使用不到索引。
select * from employees.titles where emp_no <10010&#39; and title&#61;&#39;Senior Engineer&#39; and from_date between &#39;1986-01-01&#39; and &#39;1986-12-31&#39;
6.把计算放到业务层而不是数据库层。
例如下面的 SQL 语句。
select * from doc where YEAR(create_time) <&#61; &#39;2016&#39;
即使 date 上建立了索引&#xff0c;也会全表扫描&#xff0c;可优化为值计算&#xff0c;如下&#xff1a;
select * from doc where create_time <&#61; &#39;2016-01-01&#39;
这样做不仅可以节省数据库的 CPU&#xff0c;还可以起到查询缓存优化效果。
比如下面的 SQL 语句&#xff1a;
select * from order where date <&#61; CURDATE()
可以优化为&#xff1a;
select * from order where date <&#61; &#39;2018-01-24 12:00:00&#39;
优化后的 SQL 释放了数据库的 CPU 多次调用&#xff0c;传入的 SQL 相同&#xff0c;才可以利用查询缓存。
7.强制类型转换会全表扫描
如果 phone 字段是 varchar 类型&#xff0c;则下面的 SQL 不能命中索引。
select * from user where phone&#61;13800001234
可以优化为&#xff1a;
select * from user where phone&#61;&#39;13800001234&#39;
8.更新十分频繁、数据区分度不高的字段上不宜建立索引。
9.利用覆盖索引来进行查询操作&#xff0c;避免回表。
被查询的列&#xff0c;数据能从索引中取得&#xff0c;而不用通过行定位符 row-locator 再到 row 上获取&#xff0c;即“被查询列要被所建的索引覆盖”&#xff0c;这能够加速查询速度。
例如登录业务需求&#xff0c;代码如下。
select uid, login_time from user where login_name&#61;? and passwd&#61;?
可以建立(login_name
, passwd, login_time
)的联合索引&#xff0c;由于 login_time
已经建立在索引中了&#xff0c;被查询的 uid 和 login_time
就不用去 row 上获取数据了&#xff0c;从而加速查询。
10.如果有 order by、group by 的场景&#xff0c;请注意利用索引的有序性。
WHERE a>10 ORDER BY b;
&#xff0c;索引(a,b)无法排序。11.使用短索引&#xff08;又叫前缀索引&#xff09;来优化索引。
前缀索引&#xff0c;就是用列的前缀代替整个列作为索引 key&#xff0c;当前缀长度合适时&#xff0c;可以做到既使得前缀索引的区分度接近全列索引&#xff0c;同时因为索引 key 变短而减少了索引文件的大小和维护开销&#xff0c;可以使用 count(distinct left(列名, 索引长度))/count(*) 来计算前缀索引的区分度。
前缀索引兼顾索引大小和查询速度&#xff0c;但是其缺点是不能用于 ORDER BY 和 GROUP BY 操作&#xff0c;也不能用于覆盖索引&#xff08;Covering Index&#xff0c;即当索引本身包含查询所需全部数据时&#xff0c;不再访问数据文件本身&#xff09;&#xff0c;很多时候没必要对全字段建立索引&#xff0c;根据实际文本区分度决定索引长度即可。
例如对于下面的 SQL 语句&#xff1a;
SELEC * FROM employees.employees WHERE first_name&#61;&#39;Eric&#39; AND last_name&#61;&#39;Anido&#39;;
我们可以建立索引&#xff1a;(firstname, lastname(4))。
12.建立索引的列&#xff0c;不允许为 null。
单列索引不存 null 值&#xff0c;复合索引不存全为 null 的值&#xff0c;如果列允许为 null&#xff0c;可能会得到“不符合预期”的结果集&#xff0c;所以&#xff0c;请使用 not null 约束以及默认值。
13.利用延迟关联或者子查询优化超多分页场景。
MySQL 并不是跳过 offset 行&#xff0c;而是取 offset&#43;N 行&#xff0c;然后返回放弃前 offset 行&#xff0c;返回 N 行&#xff0c;那当 offset 特别大的时候&#xff0c;效率就非常的低下&#xff0c;要么控制返回的总页数&#xff0c;要么对超过特定阈值的页数进行 SQL 改写。
示例如下&#xff0c;先快速定位需要获取的 id 段&#xff0c;然后再关联:
select a.* from 表1 a, (select id from 表1 where 条件 limit 100000,20 ) b where a.id&#61;b.id
14.业务上具有唯一特性的字段&#xff0c;即使是多个字段的组合&#xff0c;也必须建成唯一索引。
不要以为唯一索引影响了 insert 速度&#xff0c;这个速度损耗可以忽略&#xff0c;但提高查找速度是明显的。另外&#xff0c;即使在应用层做了非常完善的校验控制&#xff0c;只要没有唯一索引&#xff0c;根据墨菲定律&#xff0c;必然有脏数据产生。
15.超过三个表最好不要 join。
需要 join 的字段&#xff0c;数据类型必须一致&#xff0c;多表关联查询时&#xff0c;保证被关联的字段需要有索引。
16.如果明确知道只有一条结果返回&#xff0c;limit 1 能够提高效率。
比如如下 SQL 语句&#xff1a;
select * from user where login_name&#61;?
可以优化为&#xff1a;
select * from user where login_name&#61;? limit 1
自己明确知道只有一条结果&#xff0c;但数据库并不知道&#xff0c;明确告诉它&#xff0c;让它主动停止游标移动。
17.SQL 性能优化 explain 中的 type&#xff1a;至少要达到 range 级别&#xff0c;要求是 ref 级别&#xff0c;如果可以是 consts 最好。
18.单表索引建议控制在5个以内。
19.单索引字段数不允许超过5个。
字段超过5个时&#xff0c;实际已经起不到有效过滤数据的作用了。
20.创建索引时避免以下错误观念
这部分&#xff0c;我将列出平时会遇到的一些问题&#xff0c;并给予解答。
1. 请问如下三条 SQL 该如何建立索引&#xff1f;
where a&#61;1 and b&#61;1
where b&#61;1
where b&#61;1 order by time desc
MySQL 的查询优化器会自动调整 where 子句的条件顺序以使用适合的索引吗&#xff1f;
回答&#xff1a;
第一问&#xff1a;建议建立两个索引&#xff0c;即 idxab(a,b) 和 idxbtime(b,time)。
第二问&#xff1a;MySQL 的查询优化器会自动调整 where 子句的条件顺序以使用适合的索引&#xff0c;对于上面的第一条 SQL&#xff0c;如果建立索引为 idxba(b,a) 也是可以用到索引的&#xff0c;不过建议 where 后的字段顺序和联合索引保持一致&#xff0c;养成好习惯。
2.假如有联合索引(empno、title、fromdate)&#xff0c;下面的 SQL 是否可以用到索引&#xff0c;如果可以的话&#xff0c;会使用几个列&#xff1f;
select * from employees.titles where emp_no between &#39;10001&#39; and &#39;10010&#39; and title&#61;&#39;Senior Engineer&#39; and from_date between &#39;1986-01-01&#39; and &#39;1986-12-31&#39;
回答&#xff1a;可以使用索引&#xff0c;可以用到索引全部三个列&#xff0c;这个 SQL 看起来是用了两个范围查询&#xff0c;但作用于 empno 上的“between”实际上相当于“in”&#xff0c;也就是说 empno 实际是多值精确匹配&#xff0c;在 MySQL 中要谨慎地区分多值匹配和范围匹配&#xff0c;否则会对 MySQL 的行为产生困惑。
3.既然索引可以加快查询速度&#xff0c;那么是不是只要是查询语句需要&#xff0c;就建上索引&#xff1f;
回答&#xff1a;不是&#xff0c;因为索引虽然加快了查询速度&#xff0c;但索引也是有代价的。索引文件本身要消耗存储空间&#xff0c;同时索引会加重插入、删除和修改记录时的负担。另外&#xff0c;MySQL 在运行时也要消耗资源维护索引&#xff0c;因此索引并不是越多越好。一般两种情况下不建议建索引。第一种情况是表记录比较少&#xff0c;例如一两千条甚至只有几百条记录的表&#xff0c;没必要建索引&#xff0c;另一种是数据的区分度比较低&#xff0c;可以使用 count(distinct(列名))/count(*) 来计算区分度。
4.主键和聚集索引的关系&#xff1f;
回答&#xff1a;在 MySQL 中&#xff0c;InnoDB 引擎表是&#xff08;聚集&#xff09;索引组织表&#xff08;Clustered Index Organize Table)&#xff0c;它会先按照主键进行聚集&#xff0c;如果没有定义主键&#xff0c;InnoDB 会试着使用唯一的非空索引来代替&#xff0c;如果没有这种索引&#xff0c;InnoDB 就会定义隐藏的主键然后在上面进行聚集。由此可见&#xff0c;在 InnoDB 表中&#xff0c;主键必然是聚集索引&#xff0c;而聚集索引则未必是主键。MyISAM 引擎表是堆组织表&#xff08;Heap Organize Table)&#xff0c;它没有聚集索引的概念。
5.一个6亿的表 a&#xff0c;一个3亿的表 b&#xff0c;通过外键 tid 关联&#xff0c;如何最快的查询出满足条件的第50000到第50200中的这200条数据记录&#xff1f;
回答&#xff1a;
方法一&#xff1a;如果 a 表 tid 是自增长&#xff0c;并且是连续的&#xff0c;b表的id为索引。SQL语句如下。
select * from a,b where a.tid &#61; b.id and a.tid>500000 limit 200;
方法二&#xff1a;如果 a 表的 tid 不是连续的&#xff0c;那么就需要使用覆盖索引&#xff0c;tid 要么是主键&#xff0c;要么是辅助索引&#xff0c;b 表 id 也需要有索引。SQL语句如下。
select * from b, (select tid from a limit 50000,200) a where b.id &#61; a.tid;
6.假如建立联合索引(a,b,c)&#xff0c;下列语句是否可以使用索引&#xff0c;如果可以&#xff0c;使用了那几列&#xff1f;&#xff08;考察联合索引最左前缀原则&#xff09;
where a &#61; 3
答&#xff1a;是&#xff0c;使用了 a 列。
where a &#61; 3 and b &#61; 5
答&#xff1a;是&#xff0c;使用了 a&#xff0c;b 列。
where a &#61; 3 and c &#61; 4 and b &#61; 5
答&#xff1a;是&#xff0c;使用了 a&#xff0c;b&#xff0c;c 列。
where b &#61; 3
答&#xff1a;否。
where a &#61; 3 and c &#61; 4
答&#xff1a;是&#xff0c;使用了 a 列。
where a &#61; 3 and b > 10 and c &#61; 7
答&#xff1a;是&#xff0c;使用了 a&#xff0c;b 列。
where a &#61; 3 and b like &#39;xx%&#39; and c &#61; 7
答&#xff1a;是&#xff0c;使用了 a&#xff0c;b 列。
7.文章表的表结构如下&#xff1a;
CREATE TABLE IF NOT EXISTS &#96;article&#96; (&#96;id&#96; int(10) unsigned NOT NULL AUTO_INCREMENT,
&#96;author_id&#96; int(10) unsigned NOT NULL,
&#96;category_id&#96; int(10) unsigned NOT NULL,
&#96;views&#96; int(10) unsigned NOT NULL,
&#96;comments&#96; int(10) unsigned NOT NULL,
&#96;title&#96; varbinary(255) NOT NULL,
&#96;content&#96; text NOT NULL,
PRIMARY KEY (&#96;id&#96;)
);
下面语句应该如何建立索引&#xff1f;
select author_id, title, content from &#96;article&#96;
where category_id &#61; 1 and comments > 1
order by views desc limit 1;
回答&#xff1a;
没有联合索引时&#xff0c;explain显示&#xff0c;如下图所示&#xff1a;
创建 idxcategoryidcommentsviews(category_id,comments, views) 联合索引时&#xff0c;explain显示&#xff0c;如下图所示&#xff1a;
创建 idxcategoryidviews(categoryid,views) 联合索引&#xff0c;explain 显示&#xff0c;如下图所示&#xff1a;
由此可见&#xff0c;可以创建 idxcategoryidviews(categoryid,views) 联合索引。